﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace SourcePacker
{
    class Input
    {
        string[] ExportHeaders { get; set; }
        string[] SourcePath { get; set; }
        string[] ConditionalSourcePah { get; set; }
    }

    class CondionalSourceFolder
    {
        public CondionalSourceFolder(string folder, KeyValuePair<string, string> textArround)
        {
            Folder = folder;
            PrefixText = textArround.Key;
            SufixText = textArround.Value;
            Sources = new Dictionary<string, SourceFile>();
        }

        public string Folder { get; private set; }
        public string PrefixText { get; private set; }
        public string SufixText { get; private set; }
        public Dictionary<string, SourceFile> Sources { get; set; }
    }

    /// <summary>
    /// 源代码容器
    /// </summary>
    class SourceContainer
    {
        public Dictionary<string, SourceFile> ExportHeaders { get; private set; }
        public Dictionary<string, SourceFile> Sources { get; private set; }
        public Dictionary<string, CondionalSourceFolder> ConditionalSourceFolders { get; private set; }
        public Dictionary<string, SourceFile> ConditionalSourcesNaked
        {
            get
            {
                Dictionary<string, SourceFile> result = new Dictionary<string, SourceFile>();

                foreach (var item in ConditionalSourceFolders)
                    foreach(var source in item.Value.Sources)
                        result.Add(source.Key, source.Value);

                return result;
            }
        }

        public StringBuilder PackedExportHeader { get; private set; }
        public StringBuilder PackedSource { get; private set; }

        public long ExportHeadersTotalSize { get { return getTotalSize(ExportHeaders); } }
        public long SourcesTotalSize { get { return getTotalSize(Sources); } }

        long getTotalSize(Dictionary<string, SourceFile> sourceFiles)
        {
            long totalSize = 0;

            foreach (var source in sourceFiles)
                totalSize += source.Value.Length;

            return totalSize;
        }

        public SourceContainer(
            string[] exportHeaderFiles,
            string[] sourceFolders,
            Dictionary<string, KeyValuePair<string, string>> conditionalSourceFolders,
            string[] additionalIncludeFolders, 
            string[] sourceExtensions, 
            bool replaceSystemInclude)
        {
            ExportHeaders = new Dictionary<string, SourceFile>();
            Sources = new Dictionary<string, SourceFile>();
            ConditionalSourceFolders = new Dictionary<string, CondionalSourceFolder>();
            PackedExportHeader = new StringBuilder();
            PackedSource = new StringBuilder();

            foreach (var item in conditionalSourceFolders)
            {
                var conditionSourceFolder = new CondionalSourceFolder(item.Key, item.Value);

                ConditionalSourceFolders.Add(item.Key, conditionSourceFolder);
                addDirectory(conditionSourceFolder.Sources, item.Key, sourceExtensions);

                foreach (var source in conditionSourceFolder.Sources)
                    Sources.Add(source.Key, source.Value);
            }

            foreach (var item in sourceFolders)
                addDirectory(Sources, item, sourceExtensions);

            foreach (var item in exportHeaderFiles)
                ExportHeaders.Add(item, Sources.ContainsKey(item) ? Sources[item] : new SourceFile(item));

            analyseIncludes(Sources, ExportHeaders, additionalIncludeFolders, replaceSystemInclude);
            analyseIncludes(ExportHeaders, Sources, additionalIncludeFolders, replaceSystemInclude);
        }
        
        Dictionary<string, SourceFile> itemsAlreadyIncluded;
        bool appendFileSplitter = false;
        StringBuilder currentBuffer;

        public static bool isInFolder(string child, string parent)
        {
            return parent.Length > 0 && child.StartsWith(parent) &&
                (child[parent.Length] == Path.DirectorySeparatorChar || 
                parent[parent.Length - 1] == Path.DirectorySeparatorChar);
        }

        bool isInConditionalSourceFolders(string child)
        {
            foreach (var item in ConditionalSourceFolders)
                if (isInFolder(child, item.Key)) return true;

            return false;
        }

        public void pack(bool appendFileSplitter, bool includeExportHeaderIfNeeded, string exportHeaderFileName)
        {
            this.appendFileSplitter = appendFileSplitter;
            PackedExportHeader = new StringBuilder();
            PackedSource = new StringBuilder();

            packExportHeader();
            packSource(includeExportHeaderIfNeeded, exportHeaderFileName);
        }

        void packExportHeader()
        {
            currentBuffer = PackedExportHeader;
            itemsAlreadyIncluded = new Dictionary<string, SourceFile>();

            foreach (var source in ExportHeaders)
                if (source.Value.References.Count == 0)
                    push(source.Value);
        }

        void packSource(bool includeExportHeaderIfNeeded, string exportHeaderFileName)
        {
            currentBuffer = PackedSource;

            if (includeExportHeaderIfNeeded)
            {
                bool needed = false;

                foreach (var source in ExportHeaders)
                {
                    if (Sources.ContainsKey(source.Key))
                    {
                        needed = true;
                        break;
                    }
                }

                if (!needed) includeExportHeaderIfNeeded = false;
            }

            if (includeExportHeaderIfNeeded)
                currentBuffer.Append("\n#include \"" + exportHeaderFileName + "\"");
            else
                itemsAlreadyIncluded = new Dictionary<string, SourceFile>();

            foreach (var source in Sources)
                if (source.Value.References.Count == 0 && !isInConditionalSourceFolders(source.Key))
                    push(source.Value);

            foreach (var conditionalSourceFolder in ConditionalSourceFolders)
            {
                currentBuffer.Append('\n');
                currentBuffer.Append(conditionalSourceFolder.Value.PrefixText);
                currentBuffer.Append('\n');

                foreach (var source in conditionalSourceFolder.Value.Sources)
                    if (source.Value.References.Count == 0)
                        push(source.Value);

                currentBuffer.Append('\n');
                currentBuffer.Append(conditionalSourceFolder.Value.SufixText);
                currentBuffer.Append('\n');
            }
        }
        
        void push(SourceFile sourceFile)
        {
            itemsAlreadyIncluded.Add(sourceFile.FullPath, sourceFile);

            if (sourceFile.Includes.Count == 0)
            {
                currentBuffer.Append(sourceFile.Text);
                currentBuffer.Append('\n');
                return;
            }

            int startIndex = 0;

            foreach (var include in sourceFile.Includes)
            {
                currentBuffer.Append(sourceFile.Text.Substring(startIndex, include.StartIndex - startIndex));

                if (!itemsAlreadyIncluded.ContainsKey(include.File.FullPath))
                {
                    if (appendFileSplitter)
                        currentBuffer.Append("\n/*------- begin of file " + include.File.FileName + " -------*/\n");
                    else currentBuffer.Append('\n');

                    push(include.File);

                    if (appendFileSplitter)
                        currentBuffer.Append("\n/*-------- end of file " + include.File.FileName + " --------*/\n");
                    else currentBuffer.Append('\n');
                }
                else currentBuffer.Append('\n');

                startIndex = include.StartIndex + include.Length;
            }
            
            currentBuffer.Append(sourceFile.Text.Substring(startIndex));
        }

        
        void analyseIncludes(Dictionary<string, SourceFile> sources,
            Dictionary<string, SourceFile> sharedSources,
            string[] additionalIncludeFolders, 
            bool replaceSystemInclude)
        {
            Dictionary<string, SourceFile>[] includedFiles = new Dictionary<string, SourceFile>[2]{
                new Dictionary<string, SourceFile>(sources),
                new Dictionary<string, SourceFile>() };
            int index = 0;

            do
            {
                includedFiles[1 - index].Clear();

                foreach (var item in includedFiles[index])
                    item.Value.analyseIncludes(additionalIncludeFolders,
                        replaceSystemInclude,
                        new Dictionary<string, SourceFile>[] { sources },//, sharedSources }, 
                        ref includedFiles[1 - index]);

                if (includedFiles[1 - index].Count == 0) break;
                
                foreach(var item in includedFiles[1 - index])
                    sources.Add(item.Key, item.Value);
                
                index = 1 - index;
            } while (true);
        }

        void addDirectory(Dictionary<string, SourceFile> sources, string folder, 
            string[] sourceExtensions, bool recursive = true)
        {
            var children = Directory.GetFiles(folder, "*.*", 
                recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);

            foreach (var item in children)
                if (item != "." && item != ".." && 
                    Array.IndexOf(sourceExtensions, Path.GetExtension(item)) >= 0)
                    if (!sources.ContainsKey(item))
                        sources.Add(item, new SourceFile(item));
        }
    }
}
